package org.ithasu.filpilote.data;

import java.util.ArrayList;
import java.util.Iterator;

import org.ithasu.filpilote.data.entities.Mode;
import org.ithasu.filpilote.data.entities.Program;
import org.ithasu.filpilote.data.entities.Rule;
import org.ithasu.filpilote.data.entities.Wire;
import org.ithasu.filpilote.data.entities.Zone;
import org.ithasu.tools.Console;
import org.ithasu.tools.TimeTools;
import org.ithasu.tools.data.ERef;
import org.ithasu.tools.data.EntityTools;
import org.ithasu.tools.data.impl.DefaultJSONContext;
import org.ithasu.tools.data.impl.IterableEntityProvider;
import org.ithasu.tools.net.JSONTools;
import org.ithasu.tools.net.JSONTools.JSONContext;
import org.ithasu.tools.net.JSONTools.JSONEntity;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

public class ZoneModel implements JSONEntity {

	/** We always use COMFORT as default mode */
	private final static Mode defaultMode = Mode.COMFORT;

	// JSON properties
	private final static String JSON_WIRES = "wires";
	private final static String JSON_ZONES = "zones";
	private final static String JSON_PROGRAMS = "programs";
	private final static String JSON_CURRENT_PROGRAM = "default";

	// Entities
	private final ArrayList<Wire> wires = new ArrayList<Wire>();
	private final ArrayList<Zone> zones = new ArrayList<Zone>();
	private final ArrayList<Program> programs = new ArrayList<Program>();

	private final DefaultJSONContext context = new DefaultJSONContext();

	// State
	private final ERef<Program> currentProgram;

	public ZoneModel() {
		IterableEntityProvider<Wire> wiresProvider = new IterableEntityProvider<Wire>(wires);
		IterableEntityProvider<Zone> zonesProvider = new IterableEntityProvider<Zone>(zones);
		IterableEntityProvider<Program> programsProvider = new IterableEntityProvider<Program>(programs);

		currentProgram = new ERef<Program>(programsProvider, (Long) null);
		context.register(JSON_WIRES, wiresProvider);
		context.register(JSON_ZONES, zonesProvider);
		context.register(JSON_PROGRAMS, programsProvider);
	}

	// -- Wires ----------------------------------------

	public ArrayList<Wire> getWires() {
		return wires;
	}

	public long getWireIndex(Wire wire) {
		return wire.getId();
	}

	public String getWireName(Wire wire) {
		return wire.getName();
	}

	public Zone getWireZone(Wire wire) {
		for (Zone z : zones) {
			if (EntityTools.contains(z.getWires(), wire)) {
				return z;
			}
		}
		return null;
	}

	// -- Zones ----------------------------------------

	public ArrayList<Zone> getZones() {
		return zones;
	}

	public long getZoneIndex(Zone z) {
		return z.getId();
	}

	public String getZoneName(Zone z) {
		return z.getName();
	}

	public boolean hasWires(Zone zone) {
		return !EntityTools.isEmpty(zone.getWires());
	}

	public ArrayList<Wire> getZoneWires(Zone zone) {
		return zone.getWires().getAll();
	}

	public Mode getZoneMode(Zone zone, long time) {
		ArrayList<Rule> rules = getZoneRules(zone);
		return getModeFromRules(rules, time);
	}

	// -- Programs ----------------------------------------

	public ERef<Program> getCurrentProgram() {
		return currentProgram;
	}

	public ArrayList<Program> getPrograms() {
		return programs;
	}

	public Mode getWireMode(Wire wire, long time) {
		Zone z = getWireZone(wire);
		if (z == null) {
			return defaultMode;
		}
		return getZoneMode(z, time);
	}

	public ArrayList<Rule> getZoneRules(Zone z) {
		ArrayList<Rule> res = new ArrayList<Rule>();
		Program p = currentProgram.get();
		if (p == null) {
			return res;
		}
		for (Rule r : p.getRules()) {
			if (EntityTools.contains(r.getZones(), z)) {
				res.add(r);
			}
		}
		return res;
	}

	public ArrayList<Rule> getWireRules(Wire wire) {
		Zone z = getWireZone(wire);
		return getZoneRules(z);
	}

	private static Mode getModeFromRules(ArrayList<Rule> rules, long time) {
		int day = TimeTools.getDayOfWeek(time);
		long minutesFromMidnight = TimeTools.getMinutesFromMidnight(time);

		Long min = null;
		Mode mode = null;

		for (Rule r : rules) {
			for (int rday : r.getDays()) {
				long delta = TimeTools.getMinutesBetween(rday, r.getMinutesFromMidnight(), day, minutesFromMidnight);
				if (min == null || delta < min) {
					min = delta;
					mode = r.getNewMode();
				}
			}
		}

		return mode == null ? defaultMode : mode;
	}

	// -- Persistence ---------------------------------------------

	public void clear() {
		wires.clear();
		zones.clear();
		programs.clear();
	}

	public boolean load(String filename) {
		clear();

		JSONObject root = JSONTools.load(JSONObject.class, filename);
		if (root == null) {
			Console.message("Error in JSON at root");
			return false;
		}

		parseJSON(context, root);
		return true;
	}

	// -- JSONEntity ----------------------------------------

	@Override
	@SuppressWarnings("unchecked")
	public JSONObject toJSON(JSONContext context) {
		// Wires
		JSONArray json_wires = new JSONArray();
		for (Wire w : wires) {
			JSONObject object = w.toJSON(context);
			json_wires.add(object);
		}

		// Zones
		JSONArray json_zones = new JSONArray();
		for (Zone z : zones) {
			JSONObject object = z.toJSON(context);
			json_zones.add(object);
		}

		// Programs
		JSONArray json_programs = new JSONArray();
		for (Program p : programs) {
			JSONObject object = p.toJSON(context);
			json_programs.add(object);
		}

		// Model
		JSONObject result = new JSONObject();
		result.put(JSON_WIRES, json_wires);
		result.put(JSON_ZONES, json_zones);
		result.put(JSON_PROGRAMS, json_programs);

		return result;
	}

	@Override
	public void parseJSON(JSONContext context, JSONObject root) {
		// -- Wires --
		JSONArray json_wires = JSONTools.getProperty(JSONArray.class, root, JSON_WIRES);
		if (json_wires == null) {
			Console.error("Not found field " + JSON_WIRES + " at root");
			return;
		}

		Iterator<?> it = json_wires.iterator();
		while (it.hasNext()) {
			JSONObject json_wire = JSONTools.safeCast(JSONObject.class, it.next());
			if (json_wire != null) {
				wires.add(new Wire(context, json_wire));
			}
		}

		// -- Zones --
		JSONArray json_zones = JSONTools.getProperty(JSONArray.class, root, JSON_ZONES);
		if (json_zones == null) {
			Console.error("Not found field " + JSON_ZONES + " at root");
			return;
		}

		it = json_zones.iterator();
		while (it.hasNext()) {
			JSONObject json_zone = JSONTools.safeCast(JSONObject.class, it.next());
			if (json_zone != null) {
				zones.add(new Zone(context, json_zone));
			}
		}

		// -- Programs --
		JSONArray json_programs = JSONTools.getProperty(JSONArray.class, root, JSON_PROGRAMS);
		if (json_programs == null) {
			Console.error("Not found field " + JSON_PROGRAMS + " at root");
			return;
		}

		it = json_programs.iterator();
		while (it.hasNext()) {
			JSONObject json_program = JSONTools.safeCast(JSONObject.class, it.next());
			if (json_program != null) {
				programs.add(new Program(context, json_program));
			}
		}

		currentProgram.parseJSON(context, JSONTools.getProperty(JSONObject.class, root, JSON_CURRENT_PROGRAM));
	}

}
